package com.ikingtech.platform.service.oss.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.oss.api.OssApi;
import com.ikingtech.framework.sdk.oss.embedded.core.FileTemplate;
import com.ikingtech.framework.sdk.oss.embedded.core.OssResponse;
import com.ikingtech.framework.sdk.oss.embedded.properties.OssProperties;
import com.ikingtech.framework.sdk.oss.model.OssFileDTO;
import com.ikingtech.framework.sdk.oss.model.OssFileQueryParamDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.oss.entity.OssFileDO;
import com.ikingtech.platform.service.oss.exception.OssFileExceptionInfo;
import com.ikingtech.platform.service.oss.service.OssFileRepository;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/oss", name = "文件中心", description = "文件中心")
public class OssFileController implements OssApi {

    private final OssFileRepository repo;

    private final FileTemplate template;

    private final OssProperties properties;

    /**
     * 删除文件
     *
     * @param fileUrl 文件URL
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除文件")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String fileUrl) {
        // 查询文件实体
        OssFileDO entity = this.repo.getOne(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        if (null == entity) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        // 删除文件
        this.template.removeObject(entity.getDirName(), entity.getPath());
        // 删除文件实体
        this.repo.remove(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        return R.ok();
    }

    @Override
    public R<Object> deleteByPath(String filePath) {
        // 删除文件
        this.template.removeObject(this.properties.getDefaultBucketName(), filePath);
        return R.ok();
    }

    /**
     * 更新文件
     *
     * @param file 文件对象
     * @return 更新结果
     */
    @Override
    @OperationLog(value = "更新文件")
    @Transactional(rollbackFor = Exception.class)
    public R<OssFileDTO> update(OssFileDTO file) {
        // 检查文件是否存在
        if (!this.repo.exists(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getId, file.getId()))) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        // 更新文件信息
        this.repo.updateById(Tools.Bean.copy(file, OssFileDO.class));
        return R.ok();
    }

    /**
     * 分页查询OssFileDTO列表
     *
     * @param queryParam 查询参数
     * @return 分页结果
     */
    @Override
    public R<List<OssFileDTO>> page(OssFileQueryParamDTO queryParam) {
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()), Wrappers.<OssFileDO>lambdaQuery()
                .like(Tools.Str.isNotBlank(queryParam.getName()), OssFileDO::getOriginName, queryParam.getName())
                .eq(Tools.Str.isNotBlank(queryParam.getDirName()), OssFileDO::getDirName, queryParam.getDirName())
                .eq(Tools.Str.isNotBlank(Me.tenantCode()), OssFileDO::getTenantCode, Me.tenantCode()))).convert(entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    /**
     * 获取所有OssFileDTO对象
     *
     * @return 所有OssFileDTO对象的列表
     */
    @Override
    public R<List<OssFileDTO>> all() {
        // 将repo中的所有对象转换为OssFileDTO对象，并返回
        return R.ok(Tools.Coll.convertList(this.repo.list(Wrappers.<OssFileDO>lambdaQuery().eq(Tools.Str.isNotBlank(Me.tenantCode()), OssFileDO::getTenantCode, Me.tenantCode())), entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    /**
     * 根据id批量查询OssFileDTO列表
     *
     * @param ids 批量查询的id列表
     * @return 查询结果
     */
    @Override
    public R<List<OssFileDTO>> listByIds(BatchParam<String> ids) {
        // 如果id列表为空，则返回空列表
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        // 调用repo的listByIds方法查询id列表对应的实体列表
        // 将查询结果转换为OssFileDTO列表并返回
        return R.ok(Tools.Coll.convertList(this.repo.listByIds(ids.getList()), entity -> Tools.Bean.copy(entity, OssFileDTO.class)));
    }

    @Override
    public R<OssFileDTO> replace(MultipartFile file, String fileUrl) {
        return R.ok();
    }

    /**
     * 上传文件
     *
     * @param file 要上传的文件
     * @param dir  上传目录
     * @return 上传结果
     */
    @Override
    public R<OssFileDTO> upload(MultipartFile file, String dir) {
        // 预检查文件名
        String originalFilename = this.preCheck(file);
        try (InputStream inputStream = file.getInputStream()) {
            // 调用上传方法
            OssFileDTO result = this.upload(dir, originalFilename, inputStream, file.getContentType(), false, false);
            // 返回上传结果
            return R.ok(result);
        } catch (Exception e) {
            // 抛出异常
            throw new FrameworkException(OssFileExceptionInfo.GET_FILE_INPUT_STREAM_FAIL);
        }
    }

    /**
     * 上传文件
     *
     * @param file           要上传的文件
     * @param dir            上传目录
     * @param pathIncludeDir 路径中是否包含目录
     * @return 上传结果
     */
    @Override
    public R<OssFileDTO> upload(MultipartFile file, String dir, boolean pathIncludeDir) {
        // 预检查文件名
        String originalFilename = this.preCheck(file);
        try (InputStream inputStream = file.getInputStream()) {
            // 调用上传方法
            OssFileDTO result = this.upload(dir, originalFilename, inputStream, file.getContentType(), pathIncludeDir, false);
            // 返回上传结果
            return R.ok(result);
        } catch (Exception e) {
            // 抛出异常
            throw new FrameworkException(OssFileExceptionInfo.GET_FILE_INPUT_STREAM_FAIL);
        }
    }

    @Override
    public R<OssFileDTO> replaceByte(String fileUrl, byte[] fileByte) {
        OssFileDO entity = this.repo.getOne(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        if (null == entity) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        try {
            this.uploadFile(entity.getPath(), ByteSource.wrap(fileByte).openStream(), MediaType.APPLICATION_OCTET_STREAM_VALUE);
        } catch (NullPointerException | IOException e) {
            throw new FrameworkException(OssFileExceptionInfo.UPLOAD_FILE_ERROR);
        }
        return null;
    }

    /**
     * 上传字节数组文件
     *
     * @param fileName 文件名
     * @param dir      存储目录
     * @param fileByte 文件字节数组
     * @return 上传结果
     */
    @Override
    public R<OssFileDTO> uploadByte(String fileName, String dir, byte[] fileByte) {
        try {
            return R.ok(this.upload(dir,
                    fileName,
                    ByteSource.wrap(fileByte).openStream(),
                    MediaType.APPLICATION_OCTET_STREAM_VALUE,
                    false,
                    false));
        } catch (NullPointerException | IOException e) {
            throw new FrameworkException(OssFileExceptionInfo.UPLOAD_FILE_ERROR);
        }
    }

    /**
     * 上传字节数组文件
     *
     * @param fileName 文件名
     * @param dir      存储目录
     * @param fileByte 文件字节数组
     * @return 上传结果
     */
    @Override
    public R<OssFileDTO> uploadByte(String fileName, String dir, byte[] fileByte, boolean pathIncludeDir) {
        try {
            return R.ok(this.upload(dir,
                    fileName,
                    ByteSource.wrap(fileByte).openStream(),
                    MediaType.APPLICATION_OCTET_STREAM_VALUE,
                    pathIncludeDir,
                    false));
        } catch (NullPointerException | IOException e) {
            throw new FrameworkException(OssFileExceptionInfo.UPLOAD_FILE_ERROR);
        }
    }

    @Override
    public R<OssFileDTO> uploadByte(String fileName, String dir, byte[] fileByte, boolean pathIncludeDir, boolean originName) {
        try {
            return R.ok(this.upload(dir,
                    fileName,
                    ByteSource.wrap(fileByte).openStream(),
                    MediaType.APPLICATION_OCTET_STREAM_VALUE,
                    pathIncludeDir,
                    originName));
        } catch (NullPointerException | IOException e) {
            throw new FrameworkException(OssFileExceptionInfo.UPLOAD_FILE_ERROR);
        }
    }

    @Override
    public void download(String fileUrl, Boolean preview, HttpServletResponse response) {
        OssFileDO entity = this.repo.getOne(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        if (null == entity) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        try {
            OssResponse result = this.template.getObject(this.properties.getDefaultBucketName(), entity.getPath());
            response.setContentType(result.getContentType());
            if (!Boolean.TRUE.equals(preview)) {
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(entity.getOriginName(), Charset.defaultCharset()));
            }
            ByteStreams.copy(result.getObjectStream(), response.getOutputStream());
        } catch (Exception e) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
    }

    /**
     * 下载文件
     *
     * @param fileUrl 文件URL
     * @return 文件内容
     */
    @Override
    public R<byte[]> downloadByte(String fileUrl) {
        // 根据文件URL获取文件信息
        OssFileDO entity = this.repo.getOne(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        // 如果文件信息为空，则抛出异常
        if (null == entity) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        try {
            // 获取文件内容
            OssResponse result = this.template.getObject(this.properties.getDefaultBucketName(), entity.getPath());
            return R.ok(ByteStreams.toByteArray(result.getObjectStream()));
        } catch (Exception e) {
            // 如果获取文件内容失败，则抛出异常
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
    }

    @Override
    public R<byte[]> downloadPathByte(String filePath) {
        try {
            // 获取文件内容
            OssResponse result = this.template.getObject(this.properties.getDefaultBucketName(), filePath);
            return R.ok(ByteStreams.toByteArray(result.getObjectStream()));
        } catch (Exception e) {
            // 如果获取文件内容失败，则抛出异常
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
    }

    @Override
    public R<OssFileDTO> copy(String fileUrl) {
        // 根据文件URL获取文件信息
        OssFileDO originEntity = this.repo.getOne(Wrappers.<OssFileDO>lambdaQuery().eq(OssFileDO::getUrl, fileUrl));
        // 如果文件信息为空，则抛出异常
        if (null == originEntity) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
        String fileId = Tools.Id.uuid();
        String formattedObjectName = this.objectNameFormat(originEntity.getDirName(), fileId + "." + originEntity.getSuffix(), true, false);
        try {
            OssFileDO newEntity = Tools.Bean.copy(originEntity, OssFileDO.class);
            newEntity.setId(fileId);
            newEntity.setPath(formattedObjectName);
            newEntity.setUrl(fileId);
            // 获取文件内容
            this.template.copyObject(this.properties.getDefaultBucketName(), originEntity.getPath(), this.properties.getDefaultBucketName(), formattedObjectName, Tools.Str.EMPTY);
            this.repo.save(newEntity);
            return R.ok(Tools.Bean.copy(newEntity, OssFileDTO.class));
        } catch (Exception e) {
            // 如果获取文件内容失败，则抛出异常
            throw new FrameworkException(OssFileExceptionInfo.FILE_NOT_FOUND);
        }
    }

    /**
     * 检测指定对象是否存在
     *
     * @param path 对象路径，不包含桶名
     * @return 存在即true，否则false
     */
    @Override
    public R<Boolean> existsObject(String path) {
        return R.ok(this.template.existsObject(this.properties.getDefaultBucketName(), path));
    }

    /**
     * 上传文件到OSS
     *
     * @param dir              文件目录 例如 : /upload/images
     * @param originalFileName 原始文件名
     * @param inputStream      输入流
     * @param contentType      内容类型
     * @return OssFileDTO对象
     */
    private OssFileDTO upload(String dir,
                              String originalFileName,
                              InputStream inputStream,
                              String contentType,
                              boolean pathIncludeDir,
                              boolean originName) {
        if (Tools.Str.isBlank(originalFileName)) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_ORIGIN_NAME_CAN_NOT_BE_EMPTY);
        }
        String fileId = Tools.Id.uuid();

        long fileSize;
        try {
            // 获取输入流大小
            fileSize = inputStream.available();
        } catch (IOException ignored) {
            // 抛出异常
            throw new FrameworkException(OssFileExceptionInfo.GET_FILE_SIZE_FAIL);
        }

        // 获取文件后缀名
        String fileSuffix = originalFileName.contains(".") ? originalFileName.substring(originalFileName.lastIndexOf('.') + 1) : Tools.Str.EMPTY;
        String formattedObjectName = this.objectNameFormat(dir, originName ? originalFileName : fileId + "." + fileSuffix, pathIncludeDir, originName);
        this.uploadFile(formattedObjectName, inputStream, contentType);
        // 保存OSS文件信息
        OssFileDO entity = new OssFileDO();
        entity.setId(fileId);
        entity.setDomainCode(Me.domainCode());
        entity.setTenantCode(Me.tenantCode());
        entity.setAppCode(Me.appCode());
        entity.setDirName(dir);
        entity.setOriginName(originalFileName);
        entity.setSuffix(fileSuffix);
        entity.setFileSize(fileSize);
        entity.setUrl(fileId);
        entity.setPath(formattedObjectName);
        this.repo.save(entity);
        return Tools.Bean.copy(entity, OssFileDTO.class);
    }

    private void uploadFile(String formattedObjectName,
                            InputStream inputStream,
                            String contentType) {
        try {
            // 上传文件到OSS
            this.template.putObject(this.properties.getDefaultBucketName(), formattedObjectName, inputStream, contentType);
        } catch (Exception ignored) {
            // 抛出异常
            throw new FrameworkException(OssFileExceptionInfo.UPLOAD_FILE_ERROR);
        }
    }


    private String preCheck(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_CAN_NOT_BE_EMPTY);
        }

        String originalFilename = file.getOriginalFilename();
        if (null == originalFilename || Tools.Str.isBlank(originalFilename)) {
            throw new FrameworkException(OssFileExceptionInfo.FILE_ORIGIN_NAME_CAN_NOT_BE_EMPTY);
        }
        return originalFilename;
    }

    public String objectNameFormat(String dir, String objectName, boolean pathIncludeDir, boolean originName) {
        String formattedObjectName = objectName;
        if (!originName) {
            formattedObjectName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/" + objectName;
        }
        // 如果传入的dir不为空，则去除首位的/后拼接
        if (pathIncludeDir && Tools.Str.isNotBlank(dir)) {
            dir = Tools.Str.removePrefix(dir, "/");
            dir = Tools.Str.removeSuffix(dir, "/");
            formattedObjectName = dir + "/" + formattedObjectName;
        }
        return formattedObjectName;
    }


    /**
     * 注册系统初始化事件监听器
     */
    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        if (Boolean.FALSE.equals(this.properties.getAutoCreateBucket())) {
            return;
        }
        try {
            this.template.createBucket(this.properties.getDefaultBucketName(), Tools.Str.format("""
                    {
                         "Version": "2012-10-17",
                         "Statement": [
                             {
                                 "Effect": "Allow",
                                 "Principal": {
                                     "AWS": [
                                         "*"
                                     ]
                                 },
                                 "Action": [
                                     "s3:GetBucketLocation"
                                 ],
                                 "Resource": [
                                     "arn:aws:s3:::{}"
                                 ]
                             },
                             {
                                 "Effect": "Allow",
                                 "Principal": {
                                     "AWS": [
                                         "*"
                                     ]
                                 },
                                 "Action": [
                                     "s3:ListBucket"
                                 ],
                                 "Resource": [
                                     "arn:aws:s3:::{}"
                                 ],
                                 "Condition": {
                                     "StringEquals": {
                                         "s3:prefix": [
                                             "*"
                                         ]
                                     }
                                 }
                             },
                             {
                                 "Effect": "Allow",
                                 "Principal": {
                                     "AWS": [
                                         "*"
                                     ]
                                 },
                                 "Action": [
                                     "s3:GetObject"
                                 ],
                                 "Resource": [
                                     "arn:aws:s3:::{}/**"
                                 ]
                             }
                         ]
                     }""", this.properties.getDefaultBucketName(), this.properties.getDefaultBucketName(), this.properties.getDefaultBucketName()));
        } catch (Exception e) {
            log.info("[N/A]初始化存储桶失败[{}]", e.getMessage());
        }
    }

}
